using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using AutoMapper;
using TelerikMvcGridCustomBindingHelper.Util;

namespace TelerikMvcGridCustomBindingHelper
{
    /// <summary>
    /// Helper class for argument validation.
    /// </summary>
    public static class Guard
    {
        /// <summary>
        /// Ensures the specified argument is not null.
        /// </summary>
        /// <param name="parameter">The parameter as an expression Ex.: () => parameter.</param>
        /// <param name="errorMessage">Optional error message.</param>
        public static void IsNotNull<T>(Expression<Func<T>> parameter, string errorMessage = null) where T : class
        {
            var body = ((MemberExpression)parameter.Body);
            var value = ((FieldInfo)body.Member).GetValue(((ConstantExpression)body.Expression).Value);

            if (value != null) return;

            var paramName = body.Member.Name;

            if (errorMessage != null)
                throw new ArgumentNullException(paramName, errorMessage);

            throw new ArgumentNullException(paramName, string.Format("The parameter {0} cannot be null.", paramName));
        }

        /// <summary>
        /// Ensures the specified string is not null nor empty.
        /// </summary>
        /// <param name="parameter">The parameter as an expression Ex.: () => parameter.</param>
        /// <param name="errorMessage">Optional error message.</param>
        public static void IsNotNullOrEmpty(Expression<Func<string>> parameter, string errorMessage = null)
        {
            IsNotNull(parameter, errorMessage);

            var body = ((MemberExpression)parameter.Body);
            var value = (string)((FieldInfo)body.Member).GetValue(((ConstantExpression)body.Expression).Value);

            if (string.IsNullOrWhiteSpace(value) == false) return;

            var paramName = body.Member.Name;

            if (errorMessage != null)
                throw new ArgumentException(paramName, errorMessage);

            throw new ArgumentException(paramName, string.Format("The parameter {0} cannot be empty nor white space.", paramName));
        }

        /// <summary>
        /// Ensures that a mapping exists for the specified source and destination type parameters.
        /// </summary>
        public static void IsMapped<TSource, TDestination>()
        {
            IsMapped(typeof(TSource), typeof(TDestination));
        }

        /// <summary>
        /// Ensures that a mapping exists for the specified source and destination type parameters.
        /// </summary>
        public static void IsMapped(Type typeIn, Type typeOut)
        {
            var typeMap = AutoMapper.Mapper.FindTypeMapFor(typeIn, typeOut);
            if (typeMap == null)
            {
                throw new AutoMapperMappingException(string.Format("Mapping not found for types: \"{0}\" to \"{1}\".", typeIn.FullName, typeOut.FullName));
            }
        }

        public static GuardExpression If(bool execute)
        {
            return new GuardExpression(execute);
        }

        public static GuardExpression IfIsNull(object obj)
        {
            return new GuardExpression(obj == null);
        }
    }

    public class GuardExpression
    {
        private readonly bool _execute;

        public GuardExpression(bool execute)
        {
            _execute = execute;
        }

        public void Throw<TException>(params object[] args) where TException : Exception
        {
            if (_execute == false) return;

            Exception exception;

            try
            {
                NewExpression ctorExpression;
                ConstructorInfo ctor;

                if (args.Length > 0)
                {
                    var argse = new Expression[args.Length];
                    var parameterTypes = new Type[args.Length];

                    for (var i = 0; i < args.Length; i++)
                    {
                        argse[i] = Expression.Constant(args[i]);
                        parameterTypes[i] = args[i].GetType();
                    }

                    ctor = ObjectFactory.GetConstructor<TException>(parameterTypes);
                    ctorExpression = Expression.New(ctor, argse);
                }
                else
                {
                    ctor = ObjectFactory.GetConstructor<TException>();
                    ctorExpression = Expression.New(ctor);
                }

                exception = Expression.Lambda<Func<TException>>(ctorExpression).Compile().Invoke();
            }
            catch (Exception)
            {
                var msg = args.Where(o => o is string).Aggregate(string.Empty, (current, o) => string.Concat(current, ", " + o));

                throw new Exception(msg.Remove(0, 2));
            }

            throw exception;
        }
    }
}